#include "main.h"
#define NUM  1000
#define PORT 9998
#define ADDRESS "192.168.10.172"
#define ONLINE 100
int serverFd;
//用来epoll_ctl注册事件的结构体
struct epoll_event  ev;
//接受事件返回状态的结构体
struct epoll_event  events[NUM] = {0};
struct epoll_event  clientevents[NUM] = {0};
int clientFdsNUM = 1;
char grouparr[1024]={0};
//记录登陆成功时的用户
Flag arr[ONLINE]={0};
//当前在线人数
int account=0;
void myExit();
//负责连接的函数
void tcplianjie();
//epoll监视，实现多用户连接
void epoll();
void hand();
//接受用户的注册
void register_user(int clientfd,Msg msg);
//登陆
void login(int fd,Msg msg);
//私聊
void singel(Msg msg);
//群聊
void groupchat(Msg msg);
// 将消息写入聊天记录文件
void log_message(const char *message);
//把群聊消息转发给第一次入群的人
void display_chatlog(int fd);
//文件接收
void RecvFile(Msg msg);
//客户端下载文件
void FileDownload(int fd,char* filename);
//向用户发送服务器上的文件有哪些
void SendAllFileName(int fd);
//先向用户确定要下载哪个文件
void download(int fd,Msg msg);
// 显示私聊 聊天记录
void display_chatlog_singel(char* filename,int fd);
// 将消息写入私聊 聊天记录文件
void log_message_singel(char* filename,char *message);
int main(){
 
    signal(2,hand);
    tcplianjie();
    epoll();
   
   
    return 0;
}


void myExit(){
    close(serverFd);
    for(int i=1;i<clientFdsNUM;i++)
        if(-1 != events[i].data.fd && 0 != events[i].data.fd) 
            close(events[i].data.fd);
    exit(-1);
}

void hand(){
   //和所有客户端说分手
    for(int i=1;i<clientFdsNUM;i++)
        if(-1 != events[i].data.fd && 0 != events[i].data.fd) 
            close(events[i].data.fd);
    //服务器下线
    close(serverFd);
    exit(-2);
}
void tcplianjie(){
    //1
    serverFd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == serverFd) printf("创建socketfd失败:%m\n"),exit(-1);
    printf("创建socketfd %m\n");
    //2 协议地址簇
    struct sockaddr_in sAddr={0};
    sAddr.sin_family = AF_INET;
    sAddr.sin_addr.s_addr = inet_addr(ADDRESS);
    sAddr.sin_port = htons(PORT);//使用1W左右的

    //3 绑定

    int r = bind(serverFd,(struct sockaddr*)&sAddr,sizeof sAddr);
    if(-1 == r) printf("绑定失败:%m\n"),close(serverFd),exit(-1);
    printf("绑定 %m\n");

    //4 监听
    r = listen(serverFd,10);
    if(-1 == r) printf("监听失败:%m\n"),close(serverFd),exit(-1);
    printf("监听 %m\n");

}
//epoll监视，实现多用户连接
void epoll(){
//服务器的描述符号纳入监视范围
    ev.data.fd = serverFd;
    ev.events = EPOLLIN;
    int epfd = epoll_create(NUM);
    if(-1 == epfd) printf("创建epoll失败:%m\n"),exit(-1);
    printf("创建epoll成功!\n");

    //epoll_ctl 把服务器的fd加入监视队列
    int r = epoll_ctl(epfd,EPOLL_CTL_ADD,serverFd,&ev);
    if(-1 == r) printf("注册服务器描述符号监视失败:%m\n"),exit(-1);
    printf("注册服务器描述符号监视%m!\n");
    int clientfd;
    char buff[1024];
    char temp[1024];
    int num_fd;
    while(1){
        //第三个参数是允许的最大事件个数
        num_fd = epoll_wait(epfd,events,1024,-1);//非阻塞 
        if(0 == num_fd){
            //printf("木有动静!\n");
        }else if(-1 == num_fd){
            printf("监视失败，被发现了，赶紧跑路:%m!\n");
            myExit();
        }else{
            //检查是否哪个客户端fd有动静
            for(int i=0;i<num_fd;i++){
                if(serverFd == events[i].data.fd){
                    printf("有客户端连接事件:r:%d %d!\n",r,serverFd);
                    clientfd = accept(serverFd,NULL,NULL);  
                    if(-1 == clientfd){
                        printf("服务器崩溃:%m!");
                        myExit();
                    }
                    printf("%d连接上服务器了!\n",clientfd);
                    clientevents[clientFdsNUM-1].data.fd=clientfd;
                    clientFdsNUM++; 
                    //新连接的客户端cfd要纳入监视集合    epoll_wait不需要每次都把所有描述符号都epoll_ctl（注册）一下
                    ev.data.fd = clientfd;
                    ev.events = EPOLLIN;//EPOLLET
                    r = epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
                    if(-1 == r) printf("注册客户端描述符号%d监视失败:%m\n",clientfd),myExit();
                    printf("注册客户端描述符号%d监视%m!\n",clientfd);
                }else {
                    printf("%d客户端发数据过来!\n",events[i].data.fd);
                    Msg msg;
                    r = recv(events[i].data.fd,&msg,sizeof(msg),0);
                    int fd=events[i].data.fd;
                    if(r>0){
                        switch(msg.type){
                        case 1:  register_user(fd,msg);     break;//有客户端要注册
                        case 2:  login(fd,msg);             break;//用户要登陆
                        case 3:  groupchat(msg);            break;//用户要群聊
                        case 4:  singel(msg);               break;//是私聊
                        case 5:  RecvFile(msg);             break;//文件传输
                        case 6:  download(fd,msg);          break;//文件下载
                        }
                        
                    }else{//客户端主动断开连接
                        printf("%d已下线!\n",events[i].data.fd);
                        close(events[i].data.fd);
                        int FD=events[i].data.fd;
                        for(int j=0;j<NUM;j++){
                            if(events[i].data.fd==clientevents[j].data.fd)
                            clientevents[j].data.fd=0;  
                        }
                        //数组里重新设置为-1
                        events[i].data.fd = -1;
                        for(int a=0;a<ONLINE;a++){
                            if(FD == arr[a].socketid){//把下线的用户移出在线数组
                                arr[a].socketid=0;
                                arr[a].status=0;
                                bzero(arr[a].name,21);
                                break;
                            }
                        }
                        //服务器关闭客户端fd
                        //close(events[i].data.fd);
                        account--;
                        break;//结束当前层for循环
                    }    
                }
            }   //end of for(int i=0;i<NUM;i++){
        }//end of epoll_wait 返回值判断 if else
    }//end of while(1)
}
//接受用户的注册
void register_user(int fd,Msg msg){
    User fileUserPasswd;//文件里面的用户名和密码
    int fileID = open("register.txt",O_CREAT|O_RDWR|O_APPEND,0666);
    User res;//用这个结构体存储用户密码和账号
    res=msg.data;
    int helpnum=0;//用于分辨这个用户是否第一次注册
    //char temp[256]={0};
    Msg msg1={0};
    while(read(fileID,&fileUserPasswd,sizeof(User))!=0){
        if(strcmp(res.name,fileUserPasswd.name)==0 ){//查看这个昵称是否注册过
            helpnum=1;//注册过了                                    
            memcpy(msg1.warehouse,"昵称已存在",sizeof("昵称已存在"));
            //memcpy(msg1.warehouse,temp,sizeof(temp));
            send(fd,&msg1,sizeof msg1,0); 
            close(fileID);
            break;//判断成功，退出循环
        }
    }
    if(0==helpnum){//第一次注册
        write(fileID,&res,sizeof(User));//保存注册信息
        close(fileID);
        memcpy(msg1.warehouse,"注册成功",sizeof("注册成功"));
        printf("注册成功 昵称：%s 密码：%s\n",res.name,res.password);
       // memcpy(msg1.warehouse,temp,sizeof(temp));
        send(fd,&msg1,sizeof msg1,0);   
    }
}
//登陆
void login(int fd,Msg msg){
    User res = msg.data;//接收发送来的账号和密码
    int fileID = open("register.txt",O_RDONLY);
    if(fileID<0) printf("文件打开失败\n"),myExit();
    User fileUserPasswd;//文件里面的用户名和密码
    while(read(fileID,&fileUserPasswd,sizeof(User))!=0){
        if(strcmp(res.name,fileUserPasswd.name)==0 && 
            strcmp(res.password,fileUserPasswd.password)==0){
            for(int i=0;i<ONLINE;i++){
                if(strcmp(res.name,arr[i].name)==0){//已经登陆了
                    msg.type=888;
                    break;
                }
            }
            if(msg.type !=888){//第一次登陆
                msg.type = 666;//成功
                write(fd,&msg,sizeof(msg));//回复客户端消息
                arr[account].socketid = fd;//把通信套接字保存到数组中
                strcpy(arr[account].name,res.name);//把登录用户的名字拷贝进去
                arr[account].status = -1;//初始化用户状态为0
                account++;//登录人数增加一个
                printf("用户登录成功，登录人数为%d\n",account);
                break;//用户成功登陆，退出循环
            }  
        }
    }
    close(fileID);
    if(msg.type!=666){
        if(888 == msg.type){//重复登陆
            write(fd,&msg,sizeof(msg));//回复客户端消息
        }else{
            msg.type = 120;//登录出错了
            write(fd,&msg,sizeof(msg));//回复客户端消息   
        } 
    }

}
//群聊
void groupchat(Msg msg){
    //应该弄一个东西把要群聊的用户放在一起 可用 Flag arr[100];
    //创建groupchat.txt文件存储群聊消息
    for(int i=0;i<ONLINE;i++){
        if(strcmp(msg.data.name,arr[i].name)==0){//先找到是谁发的
            char temp_arr[1024];
            bzero(temp_arr,1024);
            Msg msg1={0};
            if(arr[i].status==2){//群里的人发的
                //接收用户消息，转发给全体在群聊中的用户
                char temp_arr1[1024];//存储要转发的数据
                bzero(temp_arr1,1024);
                sprintf(temp_arr1,"%s:%s",arr[i].name,msg.data.password);
                memcpy(msg1.warehouse,temp_arr1,sizeof(temp_arr1));
                for(int j=0;j<ONLINE;j++){//转发
                    if(arr[j].status==2){   
                        send(arr[j].socketid,&msg1,sizeof msg1,0);   
                    }
                }
                //strcpy(grouparr,temp_arr1);
                log_message(temp_arr1);
            }else{//第一次加群
                arr[i].status=2;//标记 在群里了
                //1 广播有用户加入了群聊 找到在群聊中的用户
                sprintf(temp_arr,"%s加入群聊",msg.data.name);//群公告
                memcpy(msg1.warehouse,temp_arr,sizeof(temp_arr));
                for(int j=0;j<ONLINE;j++){
                    if(arr[j].status == 2 && arr[i].name!=arr[j].name){
                        send(arr[j].socketid,&msg1,sizeof msg1,0);
                    }   
                }
               log_message(temp_arr); 
               display_chatlog(arr[i].socketid);
            }   
            //break;//找到之后就结束循环
        }//找到是谁都发的消息  
    }
    
}
//私聊
void singel(Msg msg){
    //msg.data.password 存储发送的目标
    //msg.data.name     存储是发送人的昵称
    //msg.warehouse      存储发送的数据内容
    //1.先找到发送人
    int cnt=0;//看要发送的人是否在线
    int j;
    char temp_arr1[1024];//存储要转发的数据
    bzero(temp_arr1,1024);
    Msg msg1={0};
    for(j=0;j<ONLINE;j++){//找到原发送人
        if(strcmp(msg.data.name,arr[j].name)==0){
            //检查是否第一次单聊 是 加载聊天记录 否 不需要加载
            for(int i=0;i<ONLINE;i++){
                if(strcmp(msg.data.password,arr[i].name)==0){//在线
                    cnt=1;
                    sprintf(temp_arr1,"%s给你发消息了(私聊):%s",msg.data.name,msg.warehouse);
                    memcpy(msg1.warehouse,temp_arr1,sizeof(temp_arr1));
                    send(arr[i].socketid,&msg1,sizeof msg1,0);
                    //bzero(temp_arr1,1024);
                    //sprintf(temp_arr1,"发送成功");
                    memcpy(msg1.warehouse,"发送成功",sizeof("发送成功"));
                    send(arr[j].socketid,&msg1,sizeof msg1,0);
                    //把这个消息存进发送人和接收人对应的文件夹
                    //int sendID = open(msg.data.name,O_CREAT|O_RDWR|O_APPEND,0666);//发送人 文件
                   
                    char temp_arr1[1024];//存储要存储的数据
                    bzero(temp_arr1,1024);
                    sprintf(temp_arr1,"%sto%s:%s",msg.data.name,msg.data.password,msg.warehouse );
                    memcpy(msg1.warehouse,temp_arr1,sizeof(temp_arr1));
                    log_message_singel(msg.data.name,msg1.warehouse);
                    //int recvID = open(msg.data.password,O_CREAT|O_RDWR|O_APPEND,0666);//接收人 文件
                    // char temp_arr1[1024];//存储要存储的数据
                    bzero(temp_arr1,1024);
                    sprintf(temp_arr1,"%sto%s:%s",msg.data.name,msg.data.password,msg.warehouse );
                    memcpy(msg1.warehouse,temp_arr1,sizeof(temp_arr1));
                    log_message_singel(msg.data.password,msg1.warehouse);
                    if(arr[j].status!=1){//第一次私聊
                        arr[j].status=1;// //标记为单聊状态 加载聊天记录
                        display_chatlog_singel(msg.data.name,arr[j].socketid);
                    }
                    break;
                }
            }
            break;
            
        } 
    }
    if(0==cnt){//不在线
        sprintf(temp_arr1,"%s还没有上线",msg.data.password);
        memcpy(msg1.warehouse,temp_arr1,sizeof(temp_arr1));
        send(arr[j].socketid,&msg1,sizeof msg1,0); 
    }
}
// 将消息写入聊天记录文件
void log_message_singel(char* filename,char *message) {
    FILE *logfile = fopen(filename, "a");
    if (logfile != NULL) {
        //fprintf(logfile, "%s", message,logfile);
        fwrite(message,1,1024,logfile);
        fclose(logfile);
    } else {
        perror("Failed to open log file");
    }
}
// 显示聊天记录
void display_chatlog_singel(char* filename,int fd){
    FILE *logfile = fopen(filename, "r");
    if (logfile != NULL) {
        char buffer[1024];
        int n=0;
        Msg msg1={0};
        while (fread(msg1.warehouse,1,1024, logfile) != 0) {
            send(fd,&msg1,sizeof msg1,0); 
        }
        fclose(logfile);
    } else {
        perror("Failed to open log file");
    }
}
// 将消息写入聊天记录文件
void log_message(const char *message) {
    FILE *logfile = fopen("groupchat.txt", "a");
    if (logfile != NULL) {
        //fprintf(logfile, "%s", message,logfile);
        fwrite(message,1,1024,logfile);
        fclose(logfile);
    } else {
        perror("Failed to open log file");
    }
}
// 显示聊天记录
void display_chatlog(int fd){
    FILE *logfile = fopen("groupchat.txt", "r");
    if (logfile != NULL) {
        char buffer[1024];
        int n=0;
        Msg msg1={0};
        while (fread(msg1.warehouse,1,1024, logfile) != 0) {
            send(fd,&msg1,sizeof msg1,0); 
        }
        fclose(logfile);
    } else {
        perror("Failed to open log file");
    }
}
void RecvFile(Msg msg){
    //接收文件名 创建文件 文件写入 写入字节数与文件大小进行判断
    int fds=open(msg.data.name,O_CREAT|O_WRONLY|O_APPEND,0666);
    int filesize=atoi(msg.data.password);
    printf("文件名：%s 文件大小：%d\n",msg.data.name,filesize);
    int r=write(fds,msg.warehouse,sizeof msg.warehouse);
    close(fds);
    //把文件名存储一个文件中去 filename.txt
    int helpnum=0;//看之前是否已经存过这个文件
    int fileID = open("filename.txt",O_CREAT|O_RDWR|O_APPEND,0666);
    Msg msg1={0};
    while(read(fileID,msg1.warehouse,sizeof(User))!=0){
        if(strcmp(msg.data.name,msg1.warehouse)==0 ){//查看这个昵称是否注册过
            helpnum=1;//已经写过了                                    
            break;//判断成功，退出循环
        }
    }
    if(0==helpnum){//第一次接收这个文件
        write(fileID,msg.data.name,sizeof(msg.data.name));//保存注册信息
        close(fileID);  
    }

}
void download(int fd,Msg msg){//先向用户确定要下载哪个文件
    if(strcmp(msg.warehouse,"LOOK")==0 ){//表示要查看文件
        SendAllFileName(fd);
    }else{//要下载文件
        FileDownload(fd,msg.data.name);
    }

}
//客户端下载文件
void FileDownload(int fd,char* filename){//传入客户端fd
    Msg msg1={0};
    //msg1.warehouse 存储文件的内容 msg1.data.name 存储文件名 msg1.data.password 存储文件大小
    msg1.type=6;
    strcpy(msg1.data.name, filename);
    //1.接收要下载的文件名
    struct stat file_stat;
    if (stat(msg1.data.name, &file_stat) == 0) {
        // 如果stat函数执行成功，输出文件大小
        //printf("文件大小为 %ld 字节\n", file_stat.st_size);
        sprintf(msg1.data.password,"%ld",file_stat.st_size);
        //send(fd,&msg1,sizeof msg1,0);
        long filesize=0;//统计发送的文件大小，发送完成结束循环
        //打开文件
        int fds=open(filename,O_RDONLY);
        int n=0;
        while(1){
            int r=read(fds,msg1.warehouse,sizeof msg1.warehouse);
            send(fd,&msg1,sizeof msg1,0);
            filesize+=r;
            n++;
            if(filesize == file_stat.st_size){
                printf("传输完成 n:%d\n",n);
                printf("%ld\n",filesize);
                break;
            } 
        }
    } else {
        // 如果stat函数执行失败，检查并输出错误
        perror("stat函数失败:");
        int errno;
        if (errno == ENOENT){
            printf("文件不存在\n");
            printf("请检查文件名是否正确\n");
        }
    }
}
void SendAllFileName(int fd){
    Msg msg1={0};
    msg1.type=7;
    int fileID = open("filename.txt",O_RDONLY);
    if(fileID>0){
        while(read(fileID,msg1.warehouse,sizeof(User))!=0){
            send(fd,&msg1,sizeof msg1,0);
        }
    }else{
        perror("open filename.txt");
        memcpy(msg1.warehouse,"木有文件",sizeof("木有文件"));
        send(fd,&msg1,sizeof msg1,0);
    }
}